/*
 * jngc, Java utility classes by Nicholas Coltharp
 * Copyright (C) 2010 Nicholas Coltharp <ngc1@rice.edu>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package edu.rice.ngc1.util.iterable;

import java.util.Iterator;
import java.util.NoSuchElementException;

import edu.rice.ngc1.util.annotation.Immutable;
import edu.rice.ngc1.util.annotation.Immutable.Strictness;

/**
 * <p>
 * Utility class for iterating over a range of numbers. This provides
 * an equivalent to Python's {@code range()} function -- a
 * less-verbose alternative to the standard {@code for} loop. It can
 * be especially useful in conjunction with {@link Zip} for iterating
 * over a sequence and its range at the same time.
 * </p>
 * 
 * <p>
 * Iterators produced by this object will throw
 * {@link UnsupportedOperationException} on calls to
 * {@link Iterator#remove()}.
 * </p>
 * 
 * <p>
 * Keep in mind that this class does no verification of its input
 * parameters -- it is entirely possible to create a range that will
 * go on infinitely with (for example, {@code new Range(0, -1, 1)}).
 * </p>
 */
@Immutable(Strictness.MAX)
public final class Range implements Iterable<Integer> {

    /**
     * The number that an iterator produced by this object will
     * produce.
     */
    private final int start;

    /**
     * The number that an iterator produced by this object will stop
     * at before hitting.
     */
    private final int finish;

    /**
     * The number that an iterator produced by this object will
     * increment by.
     */
    private final int step;

    /**
     * Creates a new {@code Range} object whose iterators will start
     * at the given start, stop before hitting the given finish, and
     * increment by the given step.
     * 
     * @param start
     *            the number to start at
     * @param finish
     *            the number to stop before hitting
     * @param step
     *            the number to increment by
     */
    public Range(int start, int finish, int step) {
        this.start = start;
        this.finish = finish;
        this.step = step;
    }

    /**
     * Creates a new {@code Range} object whose iterators will start
     * at the given start, increment by 1, and stop before hitting the
     * given finish.
     * 
     * @param start
     *            the number to start at
     * @param finish
     *            the number to stop before hitting
     */
    public Range(int start, int finish) {
        this(start, finish, 1);
    }

    /**
     * Creates a new {@code Range} object whose iterators will start
     * at 0, increment by 1, and stop before reaching the given
     * number.
     * 
     * @param finish
     *            the number to stop before hitting
     */
    public Range(int finish) {
        this(0, finish);
    }

    /**
     * Gets the number at which iterators produced by this object will
     * start.
     * 
     * @return this {@code Range}'s start
     */
    public int getStart() {
        return start;
    }

    /**
     * Gets the number which iterators produced by this object will
     * stop before hitting.
     * 
     * @return this {@code Range}'s finish
     */
    public int getFinish() {
        return finish;
    }

    /**
     * Gets the number by which iterators produced by this object will
     * increment with each call to {@link Iterator#next()}.
     * 
     * @return this {@code Range}'s step
     */
    public int getStep() {
        return step;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {

            int current = start;

            @Override
            public boolean hasNext() {
                if (step > 0) {
                    return (current + step) <= finish;
                }
                if (step < 0) {
                    return (current + step) >= finish;
                }
                return (current != finish);
            }

            @Override
            public Integer next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                int result = current;
                current += step;
                return result;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("cannot remove "
                        + "elements from a range");
            }
        };
    }
}
